#include <assert.h>
#include <cgreen/breadcrumb.h>
#include <cgreen/mocks.h>
#include <cgreen/boxed_double.h>
#include <inttypes.h>
// TODO: report PC-Lint bug about undeserved 451
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "cgreen_value_internal.h"
#include "cgreen/cgreen_value.h"
#include "parameters.h"
#include "constraint_internal.h"
#include "utils.h"


#ifdef __ANDROID__
#include "cgreen/internal/android_headers/androidcompat.h"
#endif // #ifdef __ANDROID__

typedef struct RecordedExpectation_ {
    const char *function;
    const char *test_file;
    int test_line;
    int time_to_live;
    CgreenVector *constraints;
    int number_times_called;
    /*
     * Used to record the number of time this particular expectation was triggered.
     * The main use at this point is to ensure that never_expect only adds itself
     * as a successful test if it as never been called
     */
    int times_triggered;
} RecordedExpectation;

const int UNLIMITED_TIME_TO_LIVE = 0x0f314159;

static CgreenMockMode cgreen_mocks_are_ = strict_mocks;
static CgreenVector *learned_mock_calls = NULL;
static CgreenVector *successfully_mocked_calls = NULL;
static CgreenVector *global_expectation_queue = NULL;

static CgreenVector *create_vector_of_actuals(va_list actuals, int count);
static CgreenVector *create_equal_value_constraints_for(CgreenVector *parameter_names,
                                                        CgreenVector *actual_values);
static CgreenVector *create_constraints_vector(void);
static RecordedExpectation *create_recorded_expectation(const char *function, const char *test_file,
                                                        int test_line, CgreenVector *constraints);
static CgreenVector *constraints_vector_from_va_list(va_list constraints);
static void destroy_expectation(RecordedExpectation *expectation);
static void ensure_expectation_queue_exists(void);
static void ensure_learned_mock_calls_list_exists(void);
static void ensure_successfully_mocked_calls_list_exists(void);
static void remove_expectation_for(const char *function);
static void trigger_unfulfilled_expectations(CgreenVector *expectation_queue, TestReporter *reporter);
static RecordedExpectation *find_expectation(const char *function);
static void apply_any_read_only_parameter_constraints(RecordedExpectation *expectation,
                                                      const char *parameter,
                                                      CgreenValue actual,
                                                      TestReporter* test_reporter);
static void apply_any_content_setting_parameter_constraints(RecordedExpectation *expectation,
                                                     const char *parameter,
                                                     CgreenValue actual,
                                                     TestReporter* test_reporter);
static CgreenValue stored_result_or_default_for(CgreenVector* constraints);
static int number_of_parameters_in(const char *parameter_list);
static bool is_always_call(RecordedExpectation* expectation);
static bool have_always_expectation_for(const char* function);
static bool is_never_call(RecordedExpectation* expectation);
static bool have_never_call_expectation_for(const char* function);
static bool remove_never_call_expectation_for(const char* function);

static void report_violated_never_call(TestReporter*, RecordedExpectation*);
static void report_unexpected_call(TestReporter*, RecordedExpectation*);
static void report_mock_parameter_name_not_found(TestReporter *test_reporter,
                                                 RecordedExpectation *expectation,
                                                 const char *parameter);
static void destroy_expectation_if_time_to_die(RecordedExpectation *expectation);

static bool is_side_effect_constraint(const Constraint *constraint);
static void apply_side_effect(TestReporter *test_reporter,
                              const RecordedExpectation *expectation,
                              Constraint *constraint);

void cgreen_mocks_are(CgreenMockMode mock_mode) {
    cgreen_mocks_are_ = mock_mode;
}


static int number_of_parameters_in(const char *parameter_list) {
    int count = 1;
    const char *current = parameter_list;

    if (strlen(parameter_list) == 0) return 0;

    while (*current != '\0') {
        if (*current == ',') count++;
        current++;
    }

    return count;
}

/* Not used anywhere, but might become handy so make it non-static to avoid warnings */
int number_of_parameter_constraints_in(const CgreenVector* constraints) {
    int i, parameters = 0;

    for (i = 0; i < cgreen_vector_size(constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(constraints, i);

        if (is_comparing(constraint)) {
           parameters++;
        }
    }

    return parameters;
}

static void learn_mock_call_for(const char *function, const char *mock_file, int mock_line,
                                CgreenVector *parameter_names, CgreenVector *actual_values) {
    CgreenVector *constraints = create_equal_value_constraints_for(parameter_names, actual_values);

    RecordedExpectation *expectation = create_recorded_expectation(function, mock_file, mock_line,
                                                                   constraints);
    ensure_learned_mock_calls_list_exists();
    cgreen_vector_add(learned_mock_calls, (void*)expectation);
}

static void handle_missing_expectation_for(const char *function, const char *mock_file, int mock_line,
                                           CgreenVector *parameter_names, CgreenVector *actual_values,
                                           TestReporter *test_reporter) {
    RecordedExpectation *expectation;
    CgreenVector *no_constraints;

    switch (cgreen_mocks_are_) {
    case loose_mocks:
        break;

    case learning_mocks:
        learn_mock_call_for(function, mock_file, mock_line, parameter_names, actual_values);
        break;

    case strict_mocks:
        no_constraints = create_constraints_vector();
        expectation = create_recorded_expectation(function, mock_file, mock_line, no_constraints);
        report_unexpected_call(test_reporter, expectation);

        destroy_expectation(expectation);
        break;
    }
}


static CgreenValue convert_boxed_double_to_cgreen_value(CgreenValue actual) {
    actual.type = CGREEN_DOUBLE;
    actual.value.double_value = unbox_double(actual.value.integer_value);
    return actual;
}

static bool parameter_is_boxed_double(CgreenVector *double_markers, int i) {
    return *(bool*)cgreen_vector_get(double_markers, i) == true;
}


static void convert_boxed_doubles_to_cgreen_values(const char *parameters, CgreenVector *actual_values) {
    CgreenVector *double_markers;
    int i;
    /* Since the caller must use 'box_double()' to pass doubles as
       arguments to 'mock()' we can know which ones are such parameters
       to be able to convert them to CgreenValues here */
    double_markers = create_vector_of_double_markers_for(parameters);
    for (i = 0; i < cgreen_vector_size(double_markers); i++) {
        if (parameter_is_boxed_double(double_markers, i)) {
            CgreenValue *actual = cgreen_vector_get(actual_values, i);
            *actual = convert_boxed_double_to_cgreen_value(*actual);
        }
    }
    destroy_cgreen_vector(double_markers);
}


intptr_t mock_(TestReporter* test_reporter, const char *function, const char *mock_file, int mock_line, const char *parameters, ...) {
    va_list actuals;
    CgreenVector *actual_values;
    CgreenVector *parameter_names;
    int failures_before_read_only_constraints_executed;
    int failures_after_read_only_constraints_executed;
    CgreenValue stored_result;
    RecordedExpectation *expectation = find_expectation(function);

    parameter_names = create_vector_of_names(parameters);

    va_start(actuals, parameters);
    actual_values = create_vector_of_actuals(actuals, number_of_parameters_in(parameters));
    va_end(actuals);

    convert_boxed_doubles_to_cgreen_values(parameters, actual_values);

    if (expectation == NULL) {
        handle_missing_expectation_for(function, mock_file, mock_line, parameter_names, actual_values, test_reporter);
        destroy_cgreen_vector(actual_values);
        destroy_cgreen_vector(parameter_names);
        return 0;
    }

    if (is_never_call(expectation)) {
        expectation->times_triggered++;
        report_violated_never_call(test_reporter, expectation);
        destroy_cgreen_vector(actual_values);
        destroy_cgreen_vector(parameter_names);
        return 0;
    }

    ensure_successfully_mocked_calls_list_exists();
    cgreen_vector_add(successfully_mocked_calls, (void*)function);

    stored_result = stored_result_or_default_for(expectation->constraints);
    // FIXME: Should verify that return value is not a DOUBLE as `mock_()' can not
    // return them. There should also be a 'mock_double_()' which does the same except
    // returning a double.

    // First check all actual constraints for validity...
    for (int i = 0; i < cgreen_vector_size(expectation->constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(expectation->constraints, i);

        if (constraint->type == CGREEN_CALL_COUNTER_CONSTRAINT) {
            expectation->number_times_called++;
            continue;
        }

        if (!is_parameter(constraint))
            continue;

        if (!constraint_is_for_parameter_in(constraint, parameters)) {
            // if expectation parameter name isn't in parameter_names,
            // fail test and skip applying constraints unlikely to match
            report_mock_parameter_name_not_found(test_reporter, expectation, constraint->parameter_name);
            destroy_expectation_if_time_to_die(expectation);
            destroy_cgreen_vector(actual_values);
            destroy_cgreen_vector(parameter_names);

            return stored_result.value.integer_value;
        }
    }

    // Now we can do the read-only constraints.  If read-only
    // constraints aren't matching, content-setting ones might corrupt
    // memory so apply read-only ones first, and if they don't fail,
    // then do the deeper constraints
    failures_before_read_only_constraints_executed = test_reporter->failures;

    for (int i = 0; i < cgreen_vector_size(parameter_names); i++) {
        const char* parameter_name = (const char*)cgreen_vector_get(parameter_names, i);
        CgreenValue actual = *(CgreenValue*)cgreen_vector_get(actual_values, i);
        apply_any_read_only_parameter_constraints(expectation, parameter_name, actual, test_reporter);
    }

    failures_after_read_only_constraints_executed = test_reporter->failures;

    // And now we can do the content setting constraints...
    // FIXME: this comparison doesn't work because only parent
    //        processes' pass/fail counts are updated, and even then
    //        only once they read from the pipe
    if (failures_before_read_only_constraints_executed == failures_after_read_only_constraints_executed) {
        for (int i = 0; i < cgreen_vector_size(parameter_names); i++) {
            const char* parameter_name = (const char*)cgreen_vector_get(parameter_names, i);
            CgreenValue actual = *(CgreenValue*)cgreen_vector_get(actual_values, i);
            apply_any_content_setting_parameter_constraints(expectation, parameter_name, actual, test_reporter);
        }
    }

    // And finally run all side effects
    for (int i = 0; i < cgreen_vector_size(expectation->constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(expectation->constraints, i);

        if (is_side_effect_constraint(constraint)) {
            apply_side_effect(test_reporter, expectation, constraint);
            continue;
        }
    }



    destroy_cgreen_vector(parameter_names);
    destroy_cgreen_vector(actual_values);

    expectation->times_triggered++;
    destroy_expectation_if_time_to_die(expectation);

    if (stored_result.type == CGREEN_DOUBLE) {
#ifdef FUTURE
        /* TODO: for some future version we should ensure that the
           user is not trying to return a double through 'mock()' when
           there is a 'mock_double()' available, which there isn't
           yet. So then

               return unbox_double(mock(...));

           should be replaced by

               return mock_double(...);
        */
        test_reporter->assert_true(test_reporter,
                                   mock_file,
                                   mock_line,
                                   false,
                                   "Mocked function [%s] have a 'will_return_double()' expectation, "
                                   "but 'mock()' cannot return doubles; Use 'mock_double()' instead",
                                   function);
        /* But we'll return it anyway, whatever it becomes is what he will get... */
        return stored_result.value.double_value;
#else
        /* ... but for now return a boxed double since the user is probably using

               return unbox_double(mock(...));

           as is the standard way in 1.x
        */
        return box_double(stored_result.value.double_value);
#endif
    } else
        return stored_result.value.integer_value;
}

static void apply_side_effect(TestReporter *test_reporter,
                              const RecordedExpectation *expectation,
                              Constraint *constraint)
{
    CgreenValue actual = {};
    constraint->execute(
                    constraint,
                    expectation->function,
                    actual,
                    expectation->test_file,
                    expectation->test_line,
                    test_reporter);
}

static bool is_side_effect_constraint(const Constraint *constraint) {
    return constraint->type == CGREEN_CALL_CONSTRAINT;
}

static CgreenVector *create_vector_of_actuals(va_list actuals, int count) {
    int i;
    CgreenVector *actual_values = create_cgreen_vector(free);
    for (i = 0; i < count; i++) {
        uintptr_t actual = va_arg(actuals, uintptr_t);
        cgreen_vector_add(actual_values, (void*)create_cgreen_value(make_cgreen_integer_value(actual)));
    }
    return actual_values;
}


static Constraint *create_appropriate_equal_constraint_for(const char *parameter_name,
                                                           CgreenValue actual) {
    Constraint *constraint;
    if (actual.type == CGREEN_DOUBLE)
        constraint = create_equal_to_double_constraint(actual.value.double_value,
                                                       parameter_name);
    else
        constraint = create_equal_to_value_constraint(actual.value.integer_value,
                                                      parameter_name);
    return constraint;
}

static CgreenVector *create_equal_value_constraints_for(CgreenVector *parameter_names,
                                                        CgreenVector *actual_values) {
    int i;
    CgreenVector *constraints = create_constraints_vector();
    for (i = 0; i < cgreen_vector_size(parameter_names); i++) {
        const char* parameter_name = (const char*)cgreen_vector_get(parameter_names, i);
        CgreenValue actual = *(CgreenValue*)cgreen_vector_get(actual_values, i);
        Constraint *constraint = create_appropriate_equal_constraint_for(parameter_name, actual);
        cgreen_vector_add(constraints, constraint);
    }
    return constraints;
}


Constraint *when_(const char *parameter, Constraint* constraint) {
    constraint->parameter_name = parameter;
    return constraint;
}



void test_times_called(Constraint *constraint, const char *function, CgreenValue actual,
               const char *test_file, int test_line, TestReporter *reporter) {
    char *message;
    char parameter_name_actual_string[255];

    snprintf(parameter_name_actual_string, sizeof(parameter_name_actual_string) - 1, "%s", function);
    message = constraint->failure_message(constraint, parameter_name_actual_string, actual.value.integer_value);

    (*reporter->assert_true)(
            reporter,
            test_file,
            test_line,
            (*constraint->compare)(constraint, actual),
            message);

    free(message);
}

Constraint *times_(const int number_times_called) {
    Constraint * time_constraint = create_constraint();
    time_constraint->expected_value = make_cgreen_integer_value(number_times_called);
    time_constraint->expected_value_name = string_dup("times");
    time_constraint->type = CGREEN_CALL_COUNTER_CONSTRAINT;

    time_constraint->compare = &compare_want_value;
    time_constraint->execute = &test_times_called;
    time_constraint->name = "be called";
    time_constraint->size_of_expected_value = sizeof(intptr_t);
    time_constraint->expected_value_message = "\t\texpected to have been called:\t[%" PRIdPTR "] times";
    return time_constraint;
}

static void destroy_expectation_if_time_to_die(RecordedExpectation *expectation) {

    if (is_always_call(expectation)) {
        return;
    }

    /* TODO: expectation->decrement_time_to_live_if_necessary(); if (expectation->time_to_die()) { ... } */
    expectation->time_to_live--;

    if (expectation->time_to_live <= 0) {
        remove_expectation_for(expectation->function);
        destroy_expectation(expectation);
    }
}

void expect_(TestReporter* test_reporter, const char *function, const char *test_file, int test_line, ...) {
    va_list constraints;
    RecordedExpectation *expectation;
    CgreenVector *constraints_vector;

    if (have_always_expectation_for(function)) {
        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation that it will always be called a certain way; "
                "any expectations declared after an always expectation are invalid", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }

    if (have_never_call_expectation_for(function)) {
        remove_never_call_expectation_for(function);

        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation that it will never be called; "
                "any expectations declared after a never call expectation are invalid", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }


    va_start(constraints, test_line);
    constraints_vector = constraints_vector_from_va_list(constraints);
    expectation = create_recorded_expectation(function, test_file, test_line, constraints_vector);
    va_end(constraints);

    expectation->time_to_live = 1;
    for (int i = 0 ; i < cgreen_vector_size(expectation->constraints) ; i++) {
        Constraint * constraint = cgreen_vector_get(expectation->constraints, i);
        if (constraint && constraint->type == CGREEN_CALL_COUNTER_CONSTRAINT) {
            expectation->time_to_live = (int)constraint->expected_value.value.integer_value;
            break;
        }
    }
    cgreen_vector_add(global_expectation_queue, expectation);
}

void always_expect_(TestReporter* test_reporter, const char *function, const char *test_file, int test_line, ...) {
    va_list constraints;
    RecordedExpectation *expectation;
    CgreenVector *constraints_vector;

    if (have_always_expectation_for(function)) {
        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation and will always be called a certain way; "
                "any expectations declared after an always expectation are discarded", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }

    if (have_never_call_expectation_for(function)) {
        remove_never_call_expectation_for(function);

        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation that it will never be called; "
                "any expectations declared after a never call expectation are discarded", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }

    va_start(constraints, test_line);
    constraints_vector = constraints_vector_from_va_list(constraints);
    expectation = create_recorded_expectation(function, test_file, test_line, constraints_vector);
    va_end(constraints);
    expectation->time_to_live = UNLIMITED_TIME_TO_LIVE;
    cgreen_vector_add(global_expectation_queue, expectation);
}

void never_expect_(TestReporter* test_reporter, const char *function, const char *test_file, int test_line, ...) {
    va_list constraints;
    RecordedExpectation *expectation;
    CgreenVector *constraints_vector;

    if (have_always_expectation_for(function)) {
        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation and will always be called a certain way; "
                "declaring an expectation after an always expectation is not allowed", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }

    if (have_never_call_expectation_for(function)) {
        remove_never_call_expectation_for(function);

        test_reporter->assert_true(
                test_reporter,
                test_file,
                test_line,
                false,
                "Mocked function [%s] already has an expectation that it will never be called; "
                "declaring an expectation for a function after a never call expectation is not allowed", function);
        va_start(constraints, test_line);
        destroy_constraints(constraints);
        va_end(constraints);

        return;
    }

    va_start(constraints, test_line);
    constraints_vector = constraints_vector_from_va_list(constraints);
    expectation = create_recorded_expectation(function, test_file, test_line, constraints_vector);
    expectation->time_to_live = -UNLIMITED_TIME_TO_LIVE;
    cgreen_vector_add(global_expectation_queue, expectation);
}


static void report_violated_never_call(TestReporter *test_reporter, RecordedExpectation* expectation) {
    test_reporter->assert_true(
            test_reporter,
            expectation->test_file,
            expectation->test_line,
            false,
            "Mocked function [%s] has an expectation that it will never be called, but it was",
            expectation->function);
}


static bool successfully_mocked_call(const char *function_name) {
    int i;

    for (i = 0; i < cgreen_vector_size(successfully_mocked_calls); i++) {
        const char *successfully_mocked_function_name = (const char *)cgreen_vector_get(successfully_mocked_calls, i);
        if (strcmp(successfully_mocked_function_name, function_name) == 0) {
            return true;
        }
    }

    return false;
}


static void report_unexpected_call(TestReporter *test_reporter, RecordedExpectation* expectation) {
    const char *message;
    if (successfully_mocked_call(expectation->function)) {
       message = "Mocked function [%s] was called too many times";
    } else {
       message = "Mocked function [%s] did not have an expectation that it would be called";
    }

    test_reporter->assert_true(
        test_reporter,
        expectation->test_file,
        expectation->test_line,
        false,
        message,
        expectation->function);
}

void clear_mocks(void) {
    if (global_expectation_queue != NULL) {
        destroy_cgreen_vector(global_expectation_queue);
        global_expectation_queue = NULL;
    }

    if (learned_mock_calls != NULL) {
        int i;
        for (i = 0; i < cgreen_vector_size(learned_mock_calls); i++) {
            RecordedExpectation *expectation = (RecordedExpectation*)cgreen_vector_get(learned_mock_calls, i);
            destroy_expectation(expectation);
        }

        destroy_cgreen_vector(learned_mock_calls);
        learned_mock_calls = NULL;
    }
}

static void show_breadcrumb(const char *name, void *memo) {
    if (*(int *) memo > 1) {
        fprintf(stderr, "-> ");
    }
    if (*(int *) memo > 0) {
        fprintf(stderr, "%s ", name);
    }
    (*(int *) memo)++;
}


void print_learned_mocks(void) {
    int e, c, i = 0;
    CgreenBreadcrumb *breadcrumb = get_test_reporter()->breadcrumb;

    walk_breadcrumb(breadcrumb, &show_breadcrumb, (void *) &i);

    fprintf(stderr, ": Learned mocks are\n");

    if (cgreen_vector_size(learned_mock_calls) == 0) {
        fprintf(stderr, "\t<none>\n");
        return;
    }

    for (e = 0; e < cgreen_vector_size(learned_mock_calls); e++) {
        RecordedExpectation *expectation = (RecordedExpectation*)cgreen_vector_get(learned_mock_calls, e);
        const char *function_name = expectation->function;
        fprintf(stderr, "\texpect(%s", function_name);
        for (c = 0; c < cgreen_vector_size(expectation->constraints); c++) {
            Constraint *constraint = (Constraint *)cgreen_vector_get(expectation->constraints, c);
            if (constraint->expected_value.type == CGREEN_DOUBLE)
                fprintf(stderr, ", when(%s, is_equal_to_double(%f))", constraint->expected_value_name,
                        constraint->expected_value.value.double_value);
            else
                fprintf(stderr, ", when(%s, is_equal_to(%" PRIdPTR "))", constraint->expected_value_name,
                        constraint->expected_value.value.integer_value);
        }
        fprintf(stderr, ");\n");
    }
}

void tally_mocks(TestReporter *reporter) {
    if (cgreen_mocks_are_ == learning_mocks) {
        print_learned_mocks();
    }

    trigger_unfulfilled_expectations(global_expectation_queue, reporter);
    clear_mocks();
}


static CgreenVector *create_constraints_vector(void) {
    return create_cgreen_vector((GenericDestructor)&destroy_constraint);
}


static CgreenVector *constraints_vector_from_va_list(va_list constraints) {
    CgreenVector *vector = create_constraints_vector();
    Constraint *constraint;
    while ((constraint = va_arg(constraints, Constraint *)) != (Constraint *)0) {
        cgreen_vector_add(vector, constraint);
    }
    return vector;
}


static RecordedExpectation *create_recorded_expectation(const char *function, const char *test_file, int test_line, CgreenVector *constraints) {
    RecordedExpectation *expectation;

    ensure_expectation_queue_exists();
    expectation = (RecordedExpectation *)malloc(sizeof(RecordedExpectation));
    expectation->function = function;
    expectation->test_file = test_file;
    expectation->test_line = test_line;
    expectation->constraints = constraints;
    expectation->number_times_called = 0;
    expectation->times_triggered = 0;

    return expectation;
}

static void destroy_expectation(RecordedExpectation *expectation) {
    destroy_cgreen_vector(expectation->constraints);
    expectation->constraints = NULL;
    expectation->function = NULL;
    expectation->test_file = NULL;
    expectation->test_line = 0;
    expectation->time_to_live = 0;
    expectation->number_times_called = 0;
    expectation->times_triggered = 0;

    free(expectation);
}

static void ensure_successfully_mocked_calls_list_exists(void) {
    if (successfully_mocked_calls == NULL) {
        // successfully_mocked_calls are __func__, so there's nothing to destroy
        successfully_mocked_calls = create_cgreen_vector(NULL);
    }
}

static void ensure_learned_mock_calls_list_exists(void) {
    if (learned_mock_calls == NULL) {
        // learned_mock_calls are __func__, so there's nothing to destroy
        learned_mock_calls = create_cgreen_vector(NULL);
    }
}

static void ensure_expectation_queue_exists(void) {
    if (global_expectation_queue == NULL) {
        global_expectation_queue = create_cgreen_vector((GenericDestructor)&destroy_expectation);
    }
}

static void remove_expectation_for(const char *function) {
    int i;
    for (i = 0; i < cgreen_vector_size(global_expectation_queue); i++) {
        RecordedExpectation *expectation = (RecordedExpectation *)cgreen_vector_get(global_expectation_queue, i);

        if (NULL == expectation) {
            fprintf(stderr, "*** CGREEN: NULL expectation found -- maybe a previous incorrect removal?");
            continue;
        }

        if (strcmp(expectation->function, function) == 0) {
            cgreen_vector_remove(global_expectation_queue, i);
            return;
        }
    }
}

static bool is_first_call_matching(RecordedExpectation *expectation) {
    return expectation->times_triggered == 0;
}


static void trigger_unfulfilled_expectations(CgreenVector *expectation_queue, TestReporter *reporter) {
    for (int e = 0; e < cgreen_vector_size(expectation_queue); e++) {
        RecordedExpectation *expectation = (RecordedExpectation *)cgreen_vector_get(expectation_queue, e);

        if (NULL == expectation) {
            fprintf(stderr, "*** NULL unfulfilled cgreen expectation found -- maybe a previous incorrect removal?");
            continue;
        }

        if (is_always_call(expectation)) {
            continue;
        }

        if (is_never_call(expectation)) {
            if (is_first_call_matching(expectation)) {
                (*reporter->assert_true)(reporter,
                                         expectation->test_file,
                                         expectation->test_line,
                                         1,
                                         "The mocked function [%s] was never called", expectation->function);
            }
            continue;
        }

        bool call_counter_present = false;
        for (int c = 0; c < cgreen_vector_size(expectation->constraints); c++) {
            Constraint *constraint = (Constraint *) cgreen_vector_get(expectation->constraints, c);
            if(constraint->type == CGREEN_CALL_COUNTER_CONSTRAINT) {
                constraint->execute(
                        constraint,
                        expectation->function,
                        make_cgreen_integer_value(expectation->number_times_called),
                        expectation->test_file,
                        expectation->test_line,
                        get_test_reporter());
                call_counter_present = true;
            }
        }

        if (!call_counter_present)
            (*reporter->assert_true)(
                    reporter,
                    expectation->test_file,
                    expectation->test_line,
                    0,
                    "Expected call was not made to mocked function [%s]", expectation->function);
    }
}

static RecordedExpectation *find_expectation(const char *function) {
    int i;
    for (i = 0; i < cgreen_vector_size(global_expectation_queue); i++) {
        RecordedExpectation *expectation =
            (RecordedExpectation *)cgreen_vector_get(global_expectation_queue, i);

        if (strcmp(expectation->function, function) == 0) {
            return expectation;
        }
    }
    return NULL;
}

static void report_mock_parameter_name_not_found(TestReporter *test_reporter, RecordedExpectation *expectation, const char *constraint_parameter_name) {

    test_reporter->assert_true(
        test_reporter,
        expectation->test_file,
        expectation->test_line,
        false,
        "Mocked function [%s] did not define a parameter named [%s]. Did you misspell it in the expectation or forget it in the mock's argument list?",
        expectation->function,
        constraint_parameter_name);
}

static void apply_any_read_only_parameter_constraints(RecordedExpectation *expectation,
                                                      const char *parameter,
                                                      CgreenValue actual,
                                                      TestReporter* test_reporter) {
    int i;

    for (i = 0; i < cgreen_vector_size(expectation->constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(expectation->constraints, i);

        if (is_content_setting(constraint) || is_side_effect_constraint(constraint)) {
            continue;
        }

        /* TODO: we need to classify constraints better, now this
           filters on COMPARING to not execute RETURN constraints
           here...*/
        if (constraint_is_not_for_parameter(constraint, parameter)) {
            continue;
        }

        constraint->execute(
            constraint,
            expectation->function,
            actual,
            expectation->test_file,
            expectation->test_line,
            test_reporter);
    }
}

static void apply_any_content_setting_parameter_constraints(RecordedExpectation *expectation,
                                                            const char *parameter, CgreenValue actual,
                                                            TestReporter* test_reporter) {
    for (int i = 0; i < cgreen_vector_size(expectation->constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(expectation->constraints, i);

        if (constraint_is_not_for_parameter(constraint, parameter)) {
            continue;
        }

        if (constraint->type != CGREEN_CONTENT_SETTER_CONSTRAINT) {
            continue;
        }

        constraint->execute(
            constraint,
            expectation->function,
            actual,
            expectation->test_file,
            expectation->test_line,
            test_reporter);
    }
}

static CgreenValue stored_result_or_default_for(CgreenVector* constraints) {
    for (int i = 0; i < cgreen_vector_size(constraints); i++) {
        Constraint *constraint = (Constraint *)cgreen_vector_get(constraints, i);

        switch (constraint->type) {
        case CGREEN_RETURN_VALUE_CONSTRAINT:
        case CGREEN_RETURN_POINTER_CONSTRAINT:
            return constraint->expected_value;
        case CGREEN_RETURN_BY_VALUE_CONSTRAINT: {
            /* When returning a struct by value we need to copy the struct
               pointed out by the Cgreen pointer value so that it does
               not get lost when the constraint is destroyed. The user
               will be responsible for deallocating the copy of the struct. */
            CgreenValue returnable = constraint->expected_value;
            void *the_struct = malloc(returnable.value_size);
            memcpy(the_struct, returnable.value.pointer_value, returnable.value_size);
            returnable.value.pointer_value = the_struct;
            return returnable;
        }
        default:
            break;
        }
    }
    return (CgreenValue){CGREEN_INTEGER, {0}, 0};
}

static bool is_always_call(RecordedExpectation* expectation) {
    return expectation->time_to_live == UNLIMITED_TIME_TO_LIVE;
}

static bool have_always_expectation_for(const char* function) {
    for (int i = 0; i < cgreen_vector_size(global_expectation_queue); i++) {
        RecordedExpectation *expectation =
                (RecordedExpectation *)cgreen_vector_get(global_expectation_queue, i);
        if (strcmp(expectation->function, function) == 0) {
            if (is_always_call(expectation)) {
                return true;
            }
        }
    }

    return false;
}

static bool is_never_call(RecordedExpectation* expectation) {
    return expectation->time_to_live == -UNLIMITED_TIME_TO_LIVE;
}

static bool have_never_call_expectation_for(const char* function) {
    for (int i = 0; i < cgreen_vector_size(global_expectation_queue); i++) {
        RecordedExpectation *expectation =
                (RecordedExpectation *)cgreen_vector_get(global_expectation_queue, i);
        if (strcmp(expectation->function, function) == 0) {
            if (is_never_call(expectation)) {
                return true;
            }
        }
    }

    return false;
}

static bool remove_never_call_expectation_for(const char* function) {
    for (int i = 0; i < cgreen_vector_size(global_expectation_queue); i++) {
        RecordedExpectation *expectation =
                (RecordedExpectation *)cgreen_vector_get(global_expectation_queue, i);
        if (strcmp(expectation->function, function) == 0) {
            if (is_never_call(expectation)) {
                cgreen_vector_remove(global_expectation_queue, i);
                destroy_expectation(expectation);
            }
        }
    }

    return false;
}

/* vim: set ts=4 sw=4 et cindent: */
